Skip to main content

media_pp\elements\source/
app_source.rs

1use std::{sync::Arc, time::Duration};
2
3use crate::pp_log::{PpLog, pp_info};
4use crossbeam_channel::{Receiver, Sender, TrySendError, bounded, select};
5use thiserror::Error as ThisError;
6
7use crate::{
8    buffer::MediaBuffer,
9    bus::{Bus, BusEvent},
10    control::{
11        ControlMsg, ControlReceiver, RequestKind, apply_finish, apply_one, drain_control,
12        wait_out_pause,
13    },
14    element::{Element, ElementType, Source, SourceElement, element_pp_log},
15    error::Result,
16    pad::SrcPad,
17};
18
19/// Errors specific to `AppSource`. Converts into the crate-wide `Error`
20/// via `?` (see [`crate::error::Error`]).
21#[derive(Debug, ThisError)]
22pub enum AppSourceError {
23    #[error("AppSource has already ended (its Pipeline finished, or Eos was already pushed)")]
24    Closed,
25}
26
27/// A source whose data comes from application code pushing buffers in,
28/// rather than this element reading them itself — GStreamer's `appsrc`
29/// equivalent, the reverse of [`crate::elements::AppSink`]. Push encoded
30/// [`MediaBuffer::Packet`]s (straight into a decoder) or already-decoded
31/// [`MediaBuffer::Video`]/[`MediaBuffer::Audio`] (e.g. frames from a
32/// camera SDK, or synthetic test data) via [`AppSourceHandle`], from any
33/// thread — a live capture callback, a network receive loop, a test.
34///
35/// Unlike [`crate::elements::FileDemuxer`], whose blocking read can only
36/// be checked against [`ControlMsg`](crate::control::ControlMsg) once per
37/// loop iteration (see [`drain_control`]'s docs), `AppSource::run` selects
38/// on its control channel and its data channel together — a `Stop` (or
39/// any other control message) is handled the moment it arrives, even if
40/// [`AppSourceHandle::push`] never gets called again.
41///
42/// Push [`MediaBuffer::Eos`] when done, or just drop every
43/// [`AppSourceHandle`] clone — either ends `run` the same way, pushing
44/// exactly one `Eos` of its own to `src_pads()`.
45///
46/// Has no timeline of its own, so [`SourceElement::seek`] is a no-op that
47/// reports back whatever was requested as where it "landed" — nothing to
48/// reposition when the app, not a file offset, decides what comes next.
49pub struct AppSource {
50    pp_log: PpLog,
51    name: Arc<str>,
52    pad: SrcPad,
53    data_rx: Receiver<MediaBuffer>,
54}
55
56/// A cheaply-cloneable handle for pushing buffers into an [`AppSource`]
57/// from any thread — `Clone` is just two refcount bumps (`name` and the
58/// channel sender are both cheap to share).
59#[derive(Clone)]
60pub struct AppSourceHandle {
61    name: Arc<str>,
62    data_tx: Sender<MediaBuffer>,
63}
64
65impl AppSource {
66    /// `capacity` bounds how many pushed buffers may sit unconsumed before
67    /// [`AppSourceHandle::push`] blocks — same trade-off as
68    /// [`crate::queue::Queue`]'s own `capacity`.
69    pub fn new(name: impl Into<String>, capacity: usize) -> (Self, AppSourceHandle) {
70        let name: Arc<str> = name.into().into();
71        let pp_log = element_pp_log(ElementType::AppSource, &name, None);
72        pp_info!(pp_log: &pp_log, "created: capacity={capacity}");
73        let pad = SrcPad::new(format!("{name}_src"));
74        let (data_tx, data_rx) = bounded(capacity);
75        (
76            Self {
77                name: name.clone(),
78                pp_log,
79                pad,
80                data_rx,
81            },
82            AppSourceHandle { name, data_tx },
83        )
84    }
85}
86
87impl AppSourceHandle {
88    pub fn name(&self) -> Arc<str> {
89        self.name.clone()
90    }
91
92    /// Blocks until there's room in the channel, or `AppSource` (every
93    /// clone of it, e.g. after its `Pipeline` finished) is gone.
94    pub fn push(&self, buf: MediaBuffer) -> Result<()> {
95        self.data_tx
96            .send(buf)
97            .map_err(|_| AppSourceError::Closed.into())
98    }
99
100    /// Non-blocking `push`, for a live producer where falling behind
101    /// matters more than losing a buffer — e.g. a camera callback that
102    /// can't afford to stall. `Ok(false)` (not an error) means the
103    /// channel was full and `buf` was *not* sent; `Err` only means
104    /// `AppSource` itself is gone.
105    pub fn try_push(&self, buf: MediaBuffer) -> Result<bool> {
106        match self.data_tx.try_send(buf) {
107            Ok(()) => Ok(true),
108            Err(TrySendError::Full(_)) => Ok(false),
109            Err(TrySendError::Disconnected(_)) => Err(AppSourceError::Closed.into()),
110        }
111    }
112}
113
114impl Element for AppSource {
115    fn name(&self) -> Arc<str> {
116        self.name.clone()
117    }
118
119    fn element_type(&self) -> ElementType {
120        ElementType::AppSource
121    }
122
123    fn pp_log(&self) -> &PpLog {
124        &self.pp_log
125    }
126
127    fn pp_log_mut(&mut self) -> &mut PpLog {
128        &mut self.pp_log
129    }
130}
131
132impl Source for AppSource {
133    fn src_pads(&mut self) -> &mut [SrcPad] {
134        std::slice::from_mut(&mut self.pad)
135    }
136}
137
138impl SourceElement for AppSource {
139    fn run(&mut self, control: &ControlReceiver, bus: &Bus) -> Result<()> {
140        pp_info!(self, "started");
141        loop {
142            // Non-blocking first: if control is already backed up, clear
143            // it before the `select!` below picks an arbitrary ready arm
144            // (it'd be just as correct to skip straight to `select!`, but
145            // this keeps `AppSource` consistent with every other
146            // `SourceElement::run` calling `drain_control` per iteration).
147            if drain_control(control, self, bus)?.stopped {
148                pp_info!(self, "stopped");
149                return Ok(());
150            }
151
152            select! {
153                recv(control.rx) -> req => {
154                    match req {
155                        Ok(req) => {
156                            match req.kind {
157                                RequestKind::Finish => {
158                                    apply_finish(self, bus, &req.ack);
159                                    pp_info!(self, "finished");
160                                    return Ok(());
161                                }
162                                RequestKind::Control(msg) => {
163                                    if apply_one(self, bus, msg, &req.ack)? {
164                                        pp_info!(self, "stopped");
165                                        return Ok(());
166                                    }
167                                    if msg == ControlMsg::Pause
168                                        && wait_out_pause(control, self, bus)?
169                                    {
170                                        pp_info!(self, "stopped");
171                                        return Ok(());
172                                    }
173                                }
174                            }
175                        }
176                        // The Pipeline itself is gone — nothing left to drive this.
177                        Err(_) => {
178                            pp_info!(self, "run: control channel gone, ending");
179                            return Ok(());
180                        }
181                    }
182                }
183                recv(self.data_rx) -> buf => {
184                    match buf {
185                        Ok(buf) if buf.is_eos() => {
186                            pp_info!(self, "event=eos phase=source_received");
187                            break;
188                        }
189                        Ok(buf) => {
190                            if let Err(error) = self.pad.push(buf) {
191                                bus.post(
192                                    &self.pp_log,
193                                    BusEvent::Error {
194                                        element_type: ElementType::AppSource,
195                                        name: self.name.clone(),
196                                        error,
197                                    },
198                                );
199                            }
200                        }
201                        // Every `AppSourceHandle` dropped without an explicit Eos.
202                        Err(_) => {
203                            pp_info!(self, "run: every AppSourceHandle dropped, ending");
204                            break;
205                        }
206                    }
207                }
208            }
209        }
210        self.pad.push_eos(&self.pp_log)
211    }
212
213    /// No-op: `AppSource` has nothing of its own to reposition — whatever
214    /// comes next is whatever the app pushes next, not a position in a
215    /// file. Reports `target` back as where it "landed" so downstream
216    /// (e.g. a [`crate::elements::Pacer`] resetting its clock offset)
217    /// still sees a consistent [`crate::bus::BusEvent::Seeked`].
218    fn seek(&mut self, target: Duration) -> Result<Duration> {
219        Ok(target)
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use std::{
226        sync::atomic::{AtomicUsize, Ordering},
227        thread,
228    };
229
230    use super::*;
231    use crate::pipeline::Pipeline;
232
233    struct CountingSink {
234        pp_log: PpLog,
235        count: Arc<AtomicUsize>,
236    }
237
238    impl Element for CountingSink {
239        fn name(&self) -> Arc<str> {
240            "counter".into()
241        }
242
243        fn element_type(&self) -> ElementType {
244            ElementType::Other
245        }
246
247        fn pp_log(&self) -> &PpLog {
248            &self.pp_log
249        }
250
251        fn pp_log_mut(&mut self) -> &mut PpLog {
252            &mut self.pp_log
253        }
254    }
255
256    impl crate::element::Sink for CountingSink {
257        fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
258            if !buf.is_eos() {
259                self.count.fetch_add(1, Ordering::SeqCst);
260            }
261            Ok(())
262        }
263
264        fn control(&mut self, _msg: ControlMsg) -> Result<()> {
265            Ok(())
266        }
267    }
268
269    fn packet() -> MediaBuffer {
270        MediaBuffer::Packet(Arc::new(ffmpeg_next::Packet::empty()))
271    }
272
273    fn wire(source: AppSource, count: Arc<AtomicUsize>) -> Arc<Pipeline> {
274        let sink = CountingSink {
275            count,
276            pp_log: element_pp_log(ElementType::Other, "counter", None),
277        };
278        Pipeline::new("test", source, |source, ctx| {
279            let branch = ctx.branch().to(Box::new(sink))?;
280            ctx.attach(source, 0, branch)?;
281            Ok(())
282        })
283        .expect("test pipeline wiring must succeed")
284    }
285
286    #[test]
287    fn pushed_buffers_reach_downstream_then_eos_ends_it() {
288        let (source, handle) = AppSource::new("app-source", 4);
289        let count = Arc::new(AtomicUsize::new(0));
290        let pipeline = wire(source, count.clone());
291        pipeline.run();
292
293        for _ in 0..5 {
294            handle.push(packet()).unwrap();
295        }
296        handle.push(MediaBuffer::Eos).unwrap();
297
298        let events: Vec<_> = pipeline.bus().iter().collect();
299        assert!(
300            !events.iter().any(|e| matches!(e, BusEvent::Error { .. })),
301            "unexpected error event(s): {events:?}"
302        );
303        assert!(events.iter().any(|e| matches!(e, BusEvent::Eos { .. })));
304        assert_eq!(count.load(Ordering::SeqCst), 5);
305    }
306
307    #[test]
308    fn dropping_every_handle_without_eos_still_ends_cleanly() {
309        let (source, handle) = AppSource::new("app-source", 4);
310        let count = Arc::new(AtomicUsize::new(0));
311        let pipeline = wire(source, count.clone());
312        pipeline.run();
313
314        handle.push(packet()).unwrap();
315        handle.push(packet()).unwrap();
316        drop(handle); // no explicit Eos — the channel disconnecting must end `run` on its own
317
318        let events: Vec<_> = pipeline.bus().iter().collect();
319        assert!(
320            !events.iter().any(|e| matches!(e, BusEvent::Error { .. })),
321            "unexpected error event(s): {events:?}"
322        );
323        assert!(events.iter().any(|e| matches!(e, BusEvent::Eos { .. })));
324        assert_eq!(count.load(Ordering::SeqCst), 2);
325    }
326
327    /// Regression guard for the exact reason `run` selects on `control`
328    /// and its data channel together instead of just blocking on
329    /// `data_rx.recv()`: with nothing ever pushed (and no `Eos`/drop
330    /// either), a plain blocking recv would never wake up to see `Stop`
331    /// at all — this must return promptly instead of hanging.
332    #[test]
333    fn stop_ends_promptly_even_with_no_producer() {
334        let (source, _handle) = AppSource::new("app-source", 4);
335        let count = Arc::new(AtomicUsize::new(0));
336        let pipeline = wire(source, count.clone());
337        pipeline.run();
338
339        // Give the background thread a moment to actually start looping
340        // (blocked in `select!`, waiting on data that's never coming)
341        // before `stop()` lands.
342        thread::sleep(Duration::from_millis(50));
343        pipeline.stop();
344
345        let events: Vec<_> = pipeline.bus().iter().collect();
346        assert!(
347            !events.iter().any(|e| matches!(e, BusEvent::Error { .. })),
348            "unexpected error event(s): {events:?}"
349        );
350        assert_eq!(count.load(Ordering::SeqCst), 0);
351    }
352}